#!/usr/bin/python3
#******************************************************************************
# Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
# licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#     http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# ******************************************************************************/
"""
This is a helper script for working with gitee.com
"""

import sys
import os
import json
import stat
import yaml
from src.libs.logger import logger
from src.config import global_config, constant
from src.libs.base import http
from src.libs.exception import OectProcessEventException
from src.libs.csvrw import CSVRW


class GiteeAPI(object):
    """
    Gitee is a helper class to abstract gitee.com api
    """
    R_FLAG = os.O_RDONLY
    R_MODES = stat.S_IRUSR
    W_FLAG = os.O_WRONLY | os.O_CREAT
    W_MODES = stat.S_IWUSR | stat.S_IRUSR

    def __init__(self, version, enterprise='openEuler', use_cache=True):
        """
        Initialization

        Args: definied in gitee_api_config.yaml
            enterprise: id of enterprise
            version: version of gitee api
        
        Returns:
        
        """
        
        self._api_version = version
        self._issue_type = dict()
        self._issue_label = dict()
        self._milestones = dict()
        self._enterprise = enterprise
        self._use_cache = use_cache
        self._cache_data = dict()

        gitee_config_path = os.path.expanduser(global_config.GITEE_API_CONFIG)
        if os.path.exists(gitee_config_path):
            try:
                with os.fdopen(os.open(gitee_config_path, self.R_FLAG, self.R_MODES), "r") as fout:
                    yaml_data = yaml.safe_load(fout)
                    if self._api_version == 'v8':
                        self._token = yaml_data['v8_access_token']
                        self._issue_type = yaml_data['gitee_api_common_id']['issue_type']
                        self._issue_label = yaml_data['gitee_api_common_id']['issue_label']
                        self._milestones = yaml_data['gitee_api_common_id']['milestone']
                        self._enterprise = yaml_data['gitee_api_common_id'][enterprise]
                    if self._api_version == 'v5':
                        self._token = yaml_data['v5_access_token']
                        self._enterprise = enterprise
            except (TypeError, KeyError) as err:
                logger.error("err: %s", err)
        else:
            logger.error(f"{gitee_config_path} is not exist")
            raise OectProcessEventException(102)
        
        if self._use_cache:
            self._cache_data = self.load_cache_data()

    def load_cache_data(self):
        """
        
        Args:
        
        Returns:
        
        """
        
        cache_data = dict()
        if not os.path.exists(global_config.GITEE_OE_FULL_REPO_SIGS):
            logger.warning(f"local {global_config.GITEE_OE_FULL_REPO_SIGS} not exist")
            return cache_data
        with os.fdopen(os.open(global_config.GITEE_OE_FULL_REPO_SIGS, self.R_FLAG, self.R_MODES), "r") as fout:
            cache_data = yaml.safe_load(fout)
                
        return cache_data
            
    def _post_gitee(self, url, data):
        """
        Post new issue

        Args:
            url: url of post request
            data: data of post request

        Returns:
            dict_resp

        """

        dict_resp = dict()
        response = http.post(url, data)
        if response.status_code != 200 and response.status_code != 201:
            logger.warning(f"post {data} to {url} failed: {response.status_code}")
            return dict_resp
        
        if response.content:
            try:
                dict_resp = json.loads(response.content)
            except json.JSONDecodeError:
                logger.error("Unexpected UTF-8 BOM (decode using utf-8-sig)")
            except TypeError:
                logger.error("the json object must be str, bytes or bytearray")
            
        return dict_resp
    
    def _put_gitee(self, url, data):
        """
        Put new issue

        Args:
            url: url of post request
            data: data of post request

        Returns:
            dict_resp

        """

        dict_resp = dict()
        response = http.put(url, data)
        if response.status_code != 200 and response.status_code != 201:
            logger.warning(f"put {data} to {url} failed: {response.status_code}")
            return dict_resp
        
        if response.content:
            try:
                dict_resp = json.loads(response.content)
            except json.JSONDecodeError:
                logger.error("Unexpected UTF-8 BOM (decode using utf-8-sig)")
            except TypeError:
                logger.error("the json object must be str, bytes or bytearray")
            
        return dict_resp
    
    def get_gitee_json(self, url):
        """
        get and load gitee json response
        Args:
            url: url of get request
        Returns:
            json_resp
        """

        json_resp = []
        resp = self._get_gitee(url)
        if resp:
            try:
                json_resp = json.loads(resp)
            except json.JSONDecodeError:
                logger.error("Unexpected UTF-8 BOM (decode using utf-8-sig)")
            except json.TypeError:
                logger.error("the json object must be str, bytes or bytearray")
            
        return json_resp
        
    def _get_gitee(self, url):
        """
        get data from url
        Args:
            url: url of get request
        Returns:
        """

        response = http.get(url)
        if response.status_code != 200:
            logger.warning(f"get data form {url} failed: {response.status_code}")
            return None
        return response.content


    def get_gitee_dict(self, url, param, with_token = True):
        """
        get data of dictionary from gitee api
        Args:
            url: url: url of get request
            param: parameters
            with_token: token of request
        Returns:
            dict_resp
        """

        dict_resp = dict()
        final_params = []
        final_url = url
        if param is not None:
            for key, value in param.items():
                final_params.append(f'{key}={value}')
        if with_token:
            final_params.append(f'access_token={self._token}')

        params = '&'.join(final_params)
        final_url = final_url + params
        # logger.info(final_url)
        dict_resp = self.get_gitee_json(final_url)
        return dict_resp

    def get_enterprise_target_id(self, target, target_key, search, namespace = None):
        """
        get target id from enterprise
        Args:
            target: projects/milestones/members
            target_key: the key of dict data from api
            search: Keywords provided to narrow the query scope
        Returns:
            target_id
        """

        target_id = ''
        url = f"https://api.gitee.com/enterprises/{self._enterprise}/{target}?"
        para = {"search":search}
        pkgs_info = self.get_gitee_dict(url, para)
        # logger.info(pkgs_info)
        try:
            pkgs_info_data = pkgs_info['data']
            for data in pkgs_info_data:
                if data[target_key] == search:
                    if target == 'projects' and data['namespace']['name'] == 'src-openEuler':
                        target_id = data['id']
                        break 
                    else:
                        target_id = data['id']
        except (TypeError, KeyError):
            logger.error(f"target: {search} NOT FOUND!")

        return target_id
    
    def get_enterprise_projects_id(self, enterprise=''):
        """
        get full projects and id from enterprise
        Args:
        Returns:
            projects
        """
        
        q_enterprise = enterprise or self._enterprise
        projects = dict()
        url = f"https://api.gitee.com/enterprises/{q_enterprise}/statistics/enterprise_project_list?"
        para = {"start_date":'2023-08-21', "end_date":'2023-08-21'}
        pkgs_info = self.get_gitee_dict(url, para)
        pkgs_info_data = pkgs_info['projects']
        for data in pkgs_info_data:
            projects[data['name']] = {'id':data['id']}
            # logger.info("found [%s]'s id [%s]", data['name'], data['id'])
        logger.info("Get [%s]'s full projects successfully!", enterprise)
        
        return projects
    
    def refresh_enterprise_projects_id(self):
        """
        refresh target id from enterprise
        Args:
        Returns:
            target_id
        """

        projects = self.get_enterprise_projects_id() or dict()
        if os.path.exists(global_config.GITEE_OE_PACKAGE_ID_YAML):
            os.remove(global_config.GITEE_OE_PACKAGE_ID_YAML)
        with os.fdopen(os.open(global_config.GITEE_OE_PACKAGE_ID_YAML, self.W_FLAG, self.W_MODES), "w") as fout:
            yaml.dump(projects, fout)
        logger.info(f"Refresh {global_config.GITEE_OE_PACKAGE_ID_YAML} finshed!")
        
        return projects


    def get_openeuler_member_id(self, package):
        """
        get id of member of openeuler
        Args:
            package: gitee 
        Returns:
            
        """

        assignee_id = self._cache_data.get(package, {}).get('assignee_id', '')
        if not assignee_id:
            sig = self._cache_data.get(package, {}).get('sig', '')
            if sig == 'sig-ROS': # anchuanxu
                return '7448762'
            elif sig == 'sig-RISC-V': # misaka00251
                return '10453487'
            elif sig == 'sig-DDE': # weidong -> layne yang
                return '8462994'
            elif sig == 'sig-KDE': # peijiankang
                return '9505401'
            elif sig == 'sig-KIRAN-DESKTOP': # 
                return '5635796'
        return assignee_id


    def get_enterprise_member_ids_v8(self, enterprise=''):
        """
        refresh all members's ids and write to yaml
        Args:
        Returns:
        """

        q_enterprise = enterprise or self._enterprise
        api_url = f'https://api.gitee.com/enterprises/{q_enterprise}/members?'
        members_info = dict()
        members = self.get_gitee_dict(api_url, None)
        members_data = []
        
        try:
            members_data = members['data']
        except KeyError as err:
            logger.error(f"Refresh {global_config.GITEE_OE_MEMBERS_ID_YAML} failed: {err}")
            return members_info

        for data in members_data:
            try:
                member_user = data['username']
                members_info[member_user] = {'id':data['id'], 'email':data['email']}
            except KeyError as err:
                logger.error(f"Refresh {global_config.GITEE_OE_MEMBERS_ID_YAML} failed: {err}")
                return members_info
            
        logger.info(f"get [%s]'s members successfully!")
        return members_info
        

    def refresh_enterprise_member_ids_v8(self):
        """
        refresh all members's ids and write to yaml
        Args:
        Returns:
        """

        members_info = self.get_enterprise_member_ids_v8() or dict()
            
        if os.path.exists(global_config.GITEE_OE_MEMBERS_ID_YAML):
            os.remove(global_config.GITEE_OE_MEMBERS_ID_YAML)
            
        with os.fdopen(os.open(global_config.GITEE_OE_MEMBERS_ID_YAML, self.W_FLAG, self.W_MODES), "w") as fout:
            yaml.dump(members_info, fout)
        logger.info(f"Refresh {global_config.GITEE_OE_MEMBERS_ID_YAML} finshed!")

    def get_milestone_id(self, milestone):
        """
        get id of specific issue type
        Args:
            milestone: 
        Returns:
            milestone_id
        """
        
        milestone_id = self._milestones.get(milestone, '')
        if not milestone_id:
            try:
                milestone_id = self.get_enterprise_target_id('milestones', 'title', milestone)
                if not milestone_id:
                    logger.warning("No milestone_id found for %s", milestone)
                return milestone_id
            except KeyError as err:
                logger.error(f"target: milestone_id NOT FOUND! {err}")
        return milestone_id
    
    def get_issue_type_id(self, issue_type):
        """
        get id of specific issue type
        Args:
            issue_type: the type of issue
        Returns:
            issue_type_id
        """
        
        issue_type_id = self._issue_type.get(issue_type, 'NA')
        if issue_type_id == 'NA':
            return self._issue_type['bug']
        return issue_type_id
    
    def get_issue_label_id(self, issue_label):
        
        issue_label_id = self._issue_label.get(issue_label, 'NA')
        return issue_label_id
        

    def get_issues(self, package_id, milestone):
        """
        get issues info
        Args:
            milestone: milestone name
            author: the author of issue
        Returns:
            releated_issue
        """

        releated_issue = []
        if not package_id:
            return releated_issue

        api_url = f"https://api.gitee.com/enterprises/{self._enterprise}/projects/{package_id}/issues?"
        if milestone:
            paras = {"milestone_id":milestone}
            issues_dict = self.get_gitee_dict(api_url, paras)
            # logger.info(issues_dict)
            releated_issue = issues_dict.get('data', list())
        return releated_issue
    
    def update_issue(self, parameters):
        """
        update issue under the specified package in gitee
        Args:
            package: package name
            mile_stone: milestone name
            issue_type: the type of issue
            assignee: maintainer name
            parameters: optioanl parameters
        Returns:
            issue_url
        """
        
        import time
        necessary_paras = dict()
        issue_url = 'create issue failed'
        milestone_id = ''
        package_id = ''
        assignee_id = ''

        necessary_paras['issue_state_id'] = 751673
        parameters.update(necessary_paras)
        logger.info(parameters)

        start_time = int(time.time())
        url = f"https://api.gitee.com/enterprises/{self._enterprise}/issues/I7MQXL"
        result = self._put_gitee(url, parameters)
        try:
            issue_url = result['issue_url']
        except KeyError as ke:
            logger.error(f'No valid issue_url return')
        used_time = int(time.time()) - start_time
        logger.info("_post_gitee used time: %s", used_time)

    def create_issue(self, package, mile_stone, issue_type, parameters):
        """
        Create issue under the specified package in gitee
        Args:
            package: package name
            mile_stone: milestone name
            issue_type: the type of issue
            assignee: maintainer name
            parameters: optioanl parameters
        Returns:
            issue_url
        """
        
        import time
        necessary_paras = dict()
        issue_url = 'create issue failed'
        milestone_id = ''
        package_id = ''
        assignee_id = ''
        
        if self._use_cache:
            package_id = self._cache_data.get(package, {}).get('repo_id', '')
        else:
            package_id = self.get_enterprise_target_id('projects', 'name', package, 'src-openEuler')
        if not package_id:
            logger.warning("No package_id found for %s", package)
            return issue_url
        
        milestone_id = self._milestones.get(mile_stone, '')
        if not milestone_id:
            milestone_id = self.get_milestone_id(mile_stone)
            if not milestone_id:
                logger.warning("No milestone_id found for %s", mile_stone)
                return issue_url

        mile_stone_releated_issue = self.get_issues(package_id, milestone_id)
        if mile_stone_releated_issue:
            logger.warning("maybe exist same issue:")
            for issue in mile_stone_releated_issue:
                if issue['state'] == 'open':
                    logger.info(issue['title'])
                    return issue_url

        assignee_id = self.get_openeuler_member_id(package)
        if not assignee_id:
            logger.warning("No assignee_id found for %s", package)
        else:
            necessary_paras['assignee_id'] = assignee_id

        issue_type_id = self.get_issue_type_id(issue_type)
        if issue_type_id == 'NA':
            logger.warning("No issue_type_id found for %s", issue_type)
            return issue_url

        necessary_paras["access_token"] = self._token
        necessary_paras['project_id'] = package_id
        necessary_paras['milestone_id'] = milestone_id
        necessary_paras['issue_type_id'] = issue_type_id
        parameters.update(necessary_paras)
        logger.info(parameters)

        start_time = int(time.time())
        url = f"https://api.gitee.com/enterprises/{self._enterprise}/issues"
        result = self._post_gitee(url, parameters)
        try:
            issue_url = result['issue_url']
        except KeyError as ke:
            logger.error(f'No valid issue_url return')
        used_time = int(time.time()) - start_time
        logger.info("_post_gitee used time: %s", used_time)
        
        return issue_url
    
    def create_issues(self, args):
        """
        
        Args:
        
        Returns:
        
        """
        
        logger.info("start create issues")
        import time
        from src.api.obs import OpenSuseOBSAPI
        pvd = OpenSuseOBSAPI()
        build_state = args['state']
        obs_project = args['prj']
        arch = args['arch']
        mile_stone = args['mile']
        priority = 1
        if 'priority' in args:
            priority = args['priority']
        package_repos = args['names']
        label_id = self.get_issue_label_id(args['label'])
        
        for pkg in package_repos:
            start_time = int(time.time())
            title = ""
            description = ""
            if args['sys'] == 'obs':
                if build_state == 'build_failed':
                    title = f'[OBS] {pkg} build problem in {obs_project}'
                    description = constant.OBS_PROJECT_LIVE_LOG.format(
                        obs_project = obs_project,
                        package_repo = pkg,
                        arch = arch)
                elif build_state == 'install_failed':
                    title = f'[OBS] {pkg} install problem in {obs_project}'
                    description = "失败详情:"
                elif build_state == 'unresolvable':
                    title = f'[OBS] {pkg} build problem in {obs_project}'
                    description = pvd.get_prj_package_detail(obs_project, pkg, arch)
                elif build_state == 'empty':
                    title = f'[OBS] {pkg} build problem in {obs_project}'
                    description = f'{pkg} is empty in {obs_project}, please init it'
                else:
                    logger.info("invalid build state: %s", build_state)
                    return
            else:
                
                if build_state == 'build_failed':
                    title = f'[EulerMaker] {pkg} build problem in {obs_project}'
                    description = "失败详情："
                elif build_state == 'install_failed':
                    title = f'[EulerMaker] {pkg} install problem in {obs_project}'
                    description = "失败详情:"
                elif build_state == 'empty':
                    title = f'[EulerMaker] {pkg} build problem in {obs_project}'
                    description = f'{pkg} is empty in {obs_project}, please init it'
                elif build_state == 'unresolvable':
                    title = f'[EulerMaker] {pkg} build problem in {obs_project}'
                    description = "依赖未闭环："
                else:
                    logger.info("invalid build state: %s", build_state)
                    return

            used_time = int(time.time()) - start_time
            logger.info("init parameters used time: %s", used_time)
                
            parameters = dict()
            parameters['title'] = title
            parameters['description'] = description
            parameters['priority'] = priority
            parameters['label_ids'] = label_id
            
            issue_url = self.create_issue(pkg, mile_stone, 'bug', parameters)
            logger.info("issue_url: %s", issue_url)
        
    def get_repo_tree(self, package, branch='master'):
        """
        get tree of branch of package
        Args:
            package:
            branch:
        Returns:
            tree_files:
        """

        tree_files = []
        api_url = "https://api.gitee.com/api/{0}/repos/{1}/{2}/git/trees/{3}?".format(
            self._api_version, self._enterprise, package, branch)
        trees_dict = self.get_gitee_dict(api_url, None)
        tree_data = trees_dict.get('tree', list())
        tree_files = [tree_file.get('path', '') for tree_file in tree_data]
        return tree_files

    def  get_community_repos(self, orgs):
        """
        导出gitee上某个社区的repo仓列表
        Args:
            orgs: ['anolis', 'src-anolis-os', 'src-anolis-sig', 'src-anolis-module', 'anolis-education', 'src-anolis-dde',\
    'src-anolis-epao', 'anolis-challenge', 'src-anolis-ai']
        Returns:
        
        """

        community_repos = dict()
        for org in orgs:
            logger.info("get repos of %s", org)
            idx = 1
            api_url = "https://gitee.com/api/{0}/orgs/{1}/repos?".format(
                self._api_version, org)
            para = {"type":'all', 'page':idx, 'per_page':100}
            repos_list = self.get_gitee_dict(api_url, para)

            while repos_list:
                
                api_url = "https://gitee.com/api/{0}/orgs/{1}/repos?".format(
                    self._api_version, org)
                para = {"type":'all', 'page':idx, 'per_page':100}
                repos_list = self.get_gitee_dict(api_url, para)

                logger.info("get %s of %s", idx, org)
                for repo_data in repos_list:
                    full_name = repo_data.get('full_name', '')
                    community_repos[full_name] = org
                idx += 1

            if community_repos:
                res_csv_name = "%s_full_repos2.csv" % (org)
                csv_title = ["Path", 'org']
                CSVRW.save_by_row(res_csv_name, community_repos, csv_title)
                community_repos = dict()

        # res_csv_name = "anolis_full_repos.csv"
        # csv_title = ["Path", 'org']
        # CSVRW.save_by_row(res_csv_name, community_repos, csv_title)

        return community_repos